package cn.edu.hitwh.resume.web.controller;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.xhtmlrenderer.pdf.ITextRenderer;

import cn.edu.hitwh.resume.biz.service.ResumeService;
import cn.edu.hitwh.resume.biz.vo.ResumeVo;
import cn.edu.hitwh.resume.dal.dao.DisireDao;
import cn.edu.hitwh.resume.dal.dao.EducationDao;
import cn.edu.hitwh.resume.dal.dao.SeekerDao;
import cn.edu.hitwh.resume.dal.dao.SkillDao;
import cn.edu.hitwh.resume.dal.dao.TemplateDao;
import cn.edu.hitwh.resume.dal.entity.SeekerDo;
import cn.edu.hitwh.resume.dal.entity.SelfDescDo;
import cn.edu.hitwh.resume.dal.entity.TemplateDo;
import cn.edu.hitwh.resume.web.controller.vo.ResultVo;
import cn.edu.hitwh.resume.web.controller.vo.ResumeSearchVo;

import com.itextpdf.text.DocumentException;
import com.itextpdf.text.pdf.BaseFont;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;

@Controller
public class SeekerController {

	@Autowired
	SeekerDao seekerDao;
	@Autowired
	EducationDao euducationDao;
	@Autowired
	SkillDao skillDao;
	@Autowired
	DisireDao disireDao;
	@Autowired
	TemplateDao templateDao;
	
	
	@Autowired
	ResumeService resumeService;
	
	@Resource
	private ServletContext servletContext;
	
	private Configuration freemarkerCfg = null;
	
	private ITextRenderer renderer = null;
	
	@PostConstruct
	public void init() {
		
		freemarkerCfg = new Configuration(Configuration.VERSION_2_3_23);
		try {
			freemarkerCfg.setDirectoryForTemplateLoading(new File(servletContext.getRealPath("/WEB-INF/template/output/")));
		} catch (IOException e) {
			e.printStackTrace();
		}
		freemarkerCfg.setDefaultEncoding("UTF-8");
		freemarkerCfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
		
		renderer = new ITextRenderer();
		try {
			renderer.getFontResolver().addFont(
					servletContext.getRealPath("/resources/fonts/msyh.ttf"),
					BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
		} catch (DocumentException | IOException e) {
			e.printStackTrace();
		}
	}
	
	@RequestMapping(value = {"/seeker", "/seeker/", "/seeker/index"})
	public String index(HttpSession session, ModelMap model) {
		Object user = session.getAttribute("seeker");
		if (null == user) {
			return "redirect:/seeker/login";
		} else {
			model.put("user", user);
			return "page/seeker/index";
		}
	}
	
	@RequestMapping(value = "/seeker/login", method = RequestMethod.GET)
	public String loginPage() {
		return "page/seeker/login";
	}
	
	@RequestMapping(value = "/seeker/login", method = RequestMethod.POST)
	public String loginAction(@RequestParam(value = "name") String name,
							   @RequestParam(value = "pwd") String pwd,
							   HttpSession session,
							   ModelMap model) { 
		model.put("current", "login");
		SeekerDo seeker = this.seekerDao.retriveByName(name);
		if (null == seeker) {
			model.put("error", "Name does not exsit");
			return "page/seeker/login";
		} else {
			if (seeker.getPwd().equals(pwd)) {
				session.setAttribute("seeker", seeker);
				model.clear();
				return "redirect:/seeker/index";
			} else {
				model.put("error", "Password is incorrect");
				return "page/seeker/login";
			}
		}
	}
	
	@RequestMapping(value = "/seeker/logout")
	public String logout(HttpSession session, ModelMap model) {
		session.invalidate();
		return "redirect:/seeker/login";
	}
	
	@RequestMapping(value = "/seeker/register", method = RequestMethod.POST)
	public String register(@RequestParam(value = "name") String name,
							 @RequestParam(value = "pwd") String pwd,
							 @RequestParam(value = "sex") Integer sex,
							 @RequestParam(value = "birthday") String birthday,
							 @RequestParam(value = "school") String school,
							 @RequestParam(value = "major") String major,
							 @RequestParam(value = "degree") Integer degree,
							 @RequestParam(value = "workage") Integer workage,
							 @RequestParam(value = "phone") String phone,
							 @RequestParam(value = "mail") String mail,
							 @RequestParam(value = "city") String city,
							 ModelMap model) {
		model.put("current", "register");
		if (null != this.seekerDao.retriveByName(name)) {
			model.put("error", "Name already exists");
		} else {
			SeekerDo seeker = new SeekerDo();
			seeker.setName(name);
			seeker.setPwd(pwd);
			seeker.setHeadpic("/resources/img/public/default_headpic.png");
			seeker.setSex(sex);
			seeker.setBirthday(birthday);
			seeker.setSchool(school);
			seeker.setMajor(major);
			seeker.setDegree(degree);
			seeker.setWorkage(workage);
			seeker.setPhone(phone);
			seeker.setMail(mail);
			seeker.setCity(city);
			this.seekerDao.create(seeker);
			model.put("msg", "Register successfully, please login");
		}
		return "page/seeker/login";
	}
	
	@RequestMapping(value = "/seeker/list")
	@ResponseBody
	public List<SeekerDo> getList() {
		return this.seekerDao.retrive();
	}
	
	@RequestMapping(value = "/seeker/some")
	@ResponseBody
	public List<ResumeSearchVo> getSome(@RequestParam(value = "workage", required = false) Integer workage, // basic info
										 @RequestParam(value = "major", required = false) String major,
										 @RequestParam(value = "degree", required = false) Integer degree,
										 @RequestParam(value = "school", required = false) String school,  
										 @RequestParam(value = "skill", required = false) String skillName,     // skill
										 @RequestParam(value = "position", required = false) String position,   // disire
										 @RequestParam(value = "city", required = false) String city,       
										 @RequestParam(value = "jobType", required = false) Integer jobType) {
		
		Map<Integer, ResumeSearchVo> resumeMap = 
				this.seekerDao.retriveByKey(workage, major, degree, school)
							   .stream()
							   .map(seeker -> {
								   ResumeSearchVo resume = new ResumeSearchVo();
								   resume.setId(seeker.getId());
								   resume.setWorkage(seeker.getWorkage());
								   resume.setMajor(seeker.getMajor());
								   resume.setDegree(seeker.getDegree());
								   resume.setSchool(seeker.getSchool());
								   return resume;
							   }).collect(Collectors.toMap(ResumeSearchVo::getId, resume -> resume));
 		
		this.skillDao.retriveByKey(skillName)
			.forEach(skill -> {
				resumeMap.computeIfPresent(skill.getSeekerId(), (k, v) -> {
					v.setSkill(skill.getName());
					return v;
				});
				resumeMap.computeIfAbsent(skill.getSeekerId(), k -> {
					SeekerDo seeker = this.seekerDao.retriveById(skill.getSeekerId());
					ResumeSearchVo resume = new ResumeSearchVo();
				    resume.setId(seeker.getId());
				    resume.setWorkage(seeker.getWorkage());
				    resume.setMajor(resume.getMajor());
				    resume.setDegree(seeker.getDegree());
				    resume.setSchool(seeker.getSchool());
				    resume.setSkill(skill.getName());
				    return resume;
				});
			});
		this.disireDao.retriveByKey(position, city, jobType)
			.forEach(disire -> {
				resumeMap.computeIfPresent(disire.getSeekerId(), (k, v) -> {
					v.setPosition(disire.getPosition());
					v.setCity(disire.getCity());
					v.setJobType(disire.getType());
					return v;
				});
				resumeMap.computeIfAbsent(disire.getSeekerId(), k -> {
					SeekerDo seeker = this.seekerDao.retriveById(disire.getSeekerId());
					ResumeSearchVo resume = new ResumeSearchVo();
				    resume.setId(seeker.getId());
				    resume.setWorkage(seeker.getWorkage());
				    resume.setMajor(resume.getMajor());
				    resume.setDegree(seeker.getDegree());
				    resume.setSchool(seeker.getSchool());
				    resume.setPosition(disire.getPosition());
				    resume.setCity(disire.getCity());
				    resume.setJobType(disire.getType());
				    return resume;
				});
			});
		resumeMap.forEach((k, v) -> {
			System.out.println(v.getId());
			System.out.println(v.getWorkage());
			System.out.println(v.getMajor());
			System.out.println(v.getDegree());
			System.out.println(v.getSchool());
			System.out.println(v.getSkill());
			System.out.println(v.getCity());
			System.out.println(v.getJobType());
			System.out.println(v.getPosition());
		});
		
		return resumeMap.entrySet()
						 .stream()
						 .map(Map.Entry::getValue)
						 .collect(Collectors.toList());
	}
	
	@RequestMapping(value = "/seeker", method = RequestMethod.POST)
	@ResponseBody
	public ResultVo add(@RequestParam(value = "name") String name,
						@RequestParam(value = "pwd", required = false) String pwd,
						@RequestParam(value = "sex") Integer sex,
						@RequestParam(value = "birthday") String birthday,
						@RequestParam(value = "school") String school,
						@RequestParam(value = "major") String major,
						@RequestParam(value = "degree") Integer degree,
						@RequestParam(value = "workage") Integer workage,
						@RequestParam(value = "phone") String phone,
						@RequestParam(value = "mail") String mail,
						@RequestParam(value = "city") String city) {
		SeekerDo seeker = new SeekerDo();
		seeker.setName(name);
		seeker.setPwd(pwd);
		seeker.setHeadpic("/resources/img/public/default_headpic.png");
		seeker.setSex(sex);
		seeker.setBirthday(birthday);
		seeker.setSchool(school);
		seeker.setMajor(major);
		seeker.setDegree(degree);
		seeker.setWorkage(workage);
		seeker.setPhone(phone);
		seeker.setMail(mail);
		seeker.setCity(city);
		this.seekerDao.create(seeker);
		return new ResultVo(true, null, seeker);
	}
	
	@RequestMapping(value = "/seeker/id/{id}", method = RequestMethod.GET)
	@ResponseBody
	public SeekerDo getOne(@PathVariable(value = "id") Integer id) {
		return this.seekerDao.retriveById(id);
	}
	
	@RequestMapping(value = "/seeker/id/{id}", method = RequestMethod.POST)
	@ResponseBody
	public ResultVo modify(@PathVariable(value = "id") Integer id,
							@RequestParam(value = "name") String name,
							@RequestParam(value = "pwd", required = false) String pwd,
							@RequestParam(value = "headpic", required = false) String headpic,
							@RequestParam(value = "sex") Integer sex,
							@RequestParam(value = "birthday") String birthday,
							@RequestParam(value = "school") String school,
							@RequestParam(value = "major") String major,
							@RequestParam(value = "degree") Integer degree,
							@RequestParam(value = "workage") Integer workage,
							@RequestParam(value = "phone") String phone,
							@RequestParam(value = "mail") String mail,
							@RequestParam(value = "city") String city) {
		SeekerDo seeker = new SeekerDo();
		seeker.setId(id);
		seeker.setName(name);
		seeker.setPwd(pwd);
		seeker.setHeadpic(headpic);
		seeker.setSex(sex);
		seeker.setBirthday(birthday);
		seeker.setSchool(school);
		seeker.setMajor(major);
		seeker.setDegree(degree);
		seeker.setWorkage(workage);
		seeker.setPhone(phone);
		seeker.setMail(mail);
		seeker.setCity(city);
		this.seekerDao.update(seeker);
		return new ResultVo(true, null, seeker);
	}
	
	@RequestMapping(value = "/seeker/id/{id}", method = RequestMethod.DELETE) 
	@ResponseBody
	public ResultVo remove(@PathVariable(value = "id") Integer id) {
		this.seekerDao.delete(id);
		return new ResultVo();
	}
	
	@RequestMapping(value = "/seeker/id/{id}/img", method = RequestMethod.POST) 
	@ResponseBody
	public ResultVo headpic(@PathVariable("id") Integer id,
							 @RequestParam("img") MultipartFile img) {
		String imgFolderUrl = "/resources/img/" + id + "/";
		File imgFolder = new File(servletContext.getRealPath(imgFolderUrl));
		if (!imgFolder.exists()) {
			imgFolder.mkdirs();
		}
		
		String uuid = UUID.randomUUID().toString();
		File imgFile = new File(imgFolder, uuid);
		System.out.println(imgFile.getAbsolutePath());
		
		ResultVo result = new ResultVo(imgFolderUrl + uuid);
		try {
			img.transferTo(imgFile);
		} catch (IllegalStateException | IOException e) {
			result.setSucceeded(false);
			result.setMessage(e.getMessage());
		}
		return result;
	}
	
	@RequestMapping(value = "/seeker/{seekerId}/template/{templateId}/preview") 
	public String preview(@PathVariable("seekerId") Integer seekerId,
						   @PathVariable("templateId") Integer templateId,
						   ModelMap model) {
		ResumeVo resume = this.resumeService.findBySeekerId(seekerId);
		
		String[] descContents = resume.getSelfDescLst().get(0).getContent().split("\n");
		List<SelfDescDo> selfLst = Arrays.stream(descContents)
										  .map(content -> {
											  SelfDescDo self = new SelfDescDo();
											  self.setContent(content);
											  return self;
										  }).collect(Collectors.toList());
		resume.setSelfDescLst(selfLst);
		
		model.addAttribute("resume", resume);
		
		return "template/preview/tpl_" + templateId;
	}
	
	@RequestMapping(value = "/seeker/{seekerId}/template/{templateId}/pdf")
	public void outputPDF(@PathVariable("seekerId") Integer seekerId,
			   				@PathVariable("templateId") Integer templateId,
			   				HttpServletResponse response) {
		//templateId = 1; // Only this one to be generated successfully
		
		try {
			Template tpl = freemarkerCfg.getTemplate("tpl_" + templateId + ".ftl");

			ResumeVo resume = this.resumeService.findBySeekerId(seekerId);
			String[] descContents = resume.getSelfDescLst().get(0).getContent().split("\n");
			List<SelfDescDo> selfLst = Arrays.stream(descContents)
											.map(content -> {
												SelfDescDo self = new SelfDescDo();
												self.setContent(content);
												return self;
											}).collect(Collectors.toList());
			resume.setSelfDescLst(selfLst);
			
			ModelMap model = new ModelMap();
			model.put("contextPath", "http://localhost:8080" + servletContext.getContextPath());
			model.put("resume", resume);

			String tmpFolderUrl = servletContext.getRealPath("/WEB-INF/temp/" + seekerId + "/");
			File tmpFolder = new File(tmpFolderUrl);
			if (!tmpFolder.exists()) {
				tmpFolder.mkdirs();
			}

			String tmpFileName = UUID.randomUUID().toString() + ".html";
			File tempFile = new File(tmpFolder, tmpFileName);

			try (BufferedWriter writer = new BufferedWriter(
											new OutputStreamWriter(
												new FileOutputStream(tempFile), "UTF-8"))) {
				tpl.process(model, writer);
			}

			renderer.setDocument(tempFile);
			renderer.layout();
			
			response.setContentType("application/pdf");
			response.setHeader("Content-Disposition", "attachment; filename=" + tmpFileName + ".pdf");
			
			try (OutputStream outStream = response.getOutputStream()) {
				renderer.createPDF(outStream);
			}
			
			// record template used count
			TemplateDo tplDo = this.templateDao.retriveById(templateId);
			tplDo.setCount(tplDo.getCount() + 1);
			this.templateDao.update(tplDo);

		} catch (IOException | TemplateException | DocumentException e) {
			e.printStackTrace();
		}
	}
	
	@RequestMapping(value = "/templates")
	@ResponseBody
	public List<TemplateDo> getTemplates() {
		return this.templateDao.retrive();
	}
}
